// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rbchat.logic.chat_root.sendfile;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;

import com.alibaba.fastjson.JSONObject;
import com.eva.android.AndriodVersion;
import com.eva.android.FileHelperQ;
import com.eva.android.ToolKits;
import com.eva.android.UriToFileHelper;
import com.eva.android.widget.DataLoadingAsyncTask2;
import com.eva.android.widget.ImageViewActivity;
import com.eva.android.widget.WidgetUtils;
import com.eva.epc.common.file.FileHelper;
import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dto.DataFromServer;
import com.leon.lfilepickerlibrary.LFilePicker;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.MyApplication;
import com.x52im.rbchat.R;
import com.x52im.rainbowchat.http.file.dto.BigFileType;
import com.x52im.rbchat.logic.chat_root.sendimg.SendImageWrapper;
import com.x52im.rbchat.logic.chat_root.sendshortvideo.SendShortVideoProcessor;
import com.x52im.rbchat.network.http.HttpRestHelper;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadTask;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadManager;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadTaskListener;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.utils.ActivityRequestCode;
import com.x52im.rbchat.utils.IntentFactory;

import java.io.File;
import java.util.List;
import java.util.Observer;

/**
 * 发送文件消息的各种实用方法辅助类.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 4.3
 */
public class SendFileHelper
{
    public final static String TAG = SendFileHelper.class.getSimpleName()+AndriodVersion.QFlag();
    
//    /**
//     * 回调常量：选择文件
//     */
//    public final static int CHOOSE_FILE_RESULT_FROM_ACTIVITY = 999;

    /**
     * 打开文件选择界面（当系统版本低于AndriodQ时启用自定义文件选择界面，否则启用系统文件选择界面）。
     *
     * @param ac
     */
    public static void openFileChooser(Activity ac)
    {
//        if(AndriodVersion.isAndroidQ_greater$equal()){
//            openFileChooser_Q(ac);
//        }
//        else {
            new LFilePicker()
                .withActivity(ac)
                .withRequestCode(ActivityRequestCode.CHOOSE_FILE_RESULT_FROM_ACTIVITY)
                .withMutilyMode(false)
                .withMyDefualtDir(ReceivedFileHelper.getReceivedFileSavedDir())
                .start();
//        }
    }

//    /**
//     * 打开系统文件选择界面。方法适用于Andriod Q及以上版本。
//     * <p>
//     * 由于Andriod Q及以后的最新Andriod系统启用了分区存储，传统的直接读取SD卡的文件读写方式已不被允许了！
//     *
//     * @param ac 父activity
//     * @since 7.2
//     */
//    public static void openFileChooser_Q(Activity ac)
//    {
//        FileHelperQ.chooseSystemFile(ac, ActivityRequestCode.CHOOSE_FILE_RESULT_FROM_ACTIVITY_Q);
//    }

    /**
     * 要由父类调用的回调处理方法.
     *
     * @param requestCode 文件选择器请求码
     * @param resultCode 文件选择器回调结果码
     * @param data activity回调数据
     * @param usedFor 参数说明请见：{@link SendFileProcessor#usedFor}
     * @param toId 参数说明请见：{@link SendFileProcessor#toId}
     * @param toName 参数说明请见：{@link SendFileProcessor#toName}
     * @since 7.2
     */
    public static void onParantActivityResult(Activity parentActivity, int requestCode, int resultCode, Intent data, String usedFor, String toId, String toName)
    {
        if(resultCode != Activity.RESULT_OK) {
            // result is not correct
            Log.d(TAG, "【SendFile】requestCode = " + requestCode);
            Log.d(TAG, "【SendFile】resultCode = " + resultCode);
            Log.d(TAG, "【SendFile】data = " + data);
            return;
        }
        else {
            boolean usedForAnriodQ = false;
            if(data != null){
                // 表示是从系统的文件选择器中选中的文件（用于Andriod Q及以上系统时）
                usedForAnriodQ = ("1".equals(data.getStringExtra("used")));
            }

            // 低于Andriod Q时，使用的是自定义文件选择器，可以直接通过File的方式读取文件数据哦
            if(!usedForAnriodQ){
                if(data != null) {
                    List<String> list = data.getStringArrayListExtra("paths");
                    if(list != null && list.size() > 0) {
                        String filePath = list.get(0);
                        Log.i(TAG, "【文件选择(<Android Q)】选中了发送的文件"+filePath);

                        // 文件信息不完整判断
                        if(CommonUtils.isStringEmpty(filePath, true)){
                            WidgetUtils.showWithDialog(parentActivity, parentActivity.getResources().getString(R.string.general_error), "无效的文件路径，本次发送未能继续！");
                            return;
                        }

                        // ** 以下是专为图片文件添中的专门逻辑（目希望从sd卡中选择的图片文件，在发送时能进入图片消息处理逻辑，而不是当成普通用文件一概而论，从而提升用户体验！）
                        boolean isImageFile = SendFileHelper.isImageFile(FileHelper.getFileName(filePath));
                        // 如果选择的是图片文件，则先要复制一份到图片消息的临时目录中（因为接下来的图片消息处理逻辑中，会涉及到压缩等，如不复制一份，那原始文件不就被破坏掉了吗？！）
                        if(isImageFile) {// for image START
                            final String tempImageLocation = SendImageWrapper.createTempImageFileLocation();
                            if (tempImageLocation == null) {
                                WidgetUtils.showToast(parentActivity, parentActivity.getString(R.string.chat_sendpic_image_sdcar_error), WidgetUtils.ToastType.WARN);
                                return;
                            }

                            boolean copyOK = false;
                            try {
                                // 该原始相片所对应的File文件
                                File originalPhotoForChooseCopySrc = new File(filePath);
                                // 将选择的原始图片复制1份（以便发送前的压缩），本对象指向的是将要复制到的临时文件（位于私有存储，不受Andriod10系统权限限制）
                                File originalPhotoForChooseCopyDest = new File(tempImageLocation);
                                // 将选择的原始图片复制1份（以便发送前的压缩）
                                copyOK = FileHelper.copyFile(originalPhotoForChooseCopySrc, originalPhotoForChooseCopyDest);
                            } catch (Exception e) {
                                Log.e(TAG, e.getMessage(), e);
                            }

                            // 要发送的图片复制ok
                            if (copyOK) {
                                Log.i(TAG, "【文件选择(<Android Q)】此次选择的文件是图片，它的原始路径是："+filePath+"，已成功复制到图片消息的临时处理路径："+tempImageLocation+"。即将进入图片消息的完整处理和发送逻辑。。。。");

                                // 进入图片消的发送处理逻辑
                                sendFileImpl(parentActivity, usedFor, toId, toName, tempImageLocation);
                            } else
                                WidgetUtils.showToast(parentActivity, parentActivity.getString(R.string.chat_sendpic_image_sdcar_error)+"[copy faild!]", WidgetUtils.ToastType.WARN);
                        }// for image END
                        else {
                            // 进入普通大文件消息的发送处理逻辑
                            sendFileImpl(parentActivity, usedFor, toId, toName, filePath);
                        }
                    }
                }
                else {
                    Log.w(TAG, "没有选中有效的文件路径，发送没有继续！");
                }
            }
            // Andriod Q或更高系统时，使用的是系统自带文件选择器，受限于分区存储权限限制，对文件数据的读取是不一样的
            else{
                if (data == null || data.getData() == null)
                    return;

                //** ================================== ▼ 先读取已选择文件的Uri

                // 选择的原始文件Uri
                Uri originalPhotoForChoose = null;
                if (AndriodVersion.isAndroidKitKat_greater$equal()) {
                    // 保存Uri
                    originalPhotoForChoose = data.getData();
                    // Andriod 4.4及以上系统需要额外进行的处理
                    final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    parentActivity.getContentResolver().takePersistableUriPermission(originalPhotoForChoose, takeFlags);
                } else {
                    // 保存Uri
                    originalPhotoForChoose = data.getData();
                }

                //** ================================== ▼ 接着开始文件的复制、发送流程
                //** 补充说明：因大文件发送涉及断点续传、多文件同时异步上传等复杂逻辑，且通过Andriod10里的ContentResolver读取
                //**          分区存储中的公共目录时无法支持RandomFileAccess随机读取（也就无法实现断点续传），所以此处针对Andriod10
                //**          及以后系统的文件发送功能是先复制到私有APP的目录，针对复制到私有目录后的文件用老的File方式读写就完全可以
                //**          对接上之前的大文件发送完整流程了。这个针对分区存储的适与方法是代码改动最小、最稳妥的方式，唯一不够完美的
                //**          就是多了个"复制"过程，但经实测复制速度很快，加上异步和ui的体验优化，功能体验上对用户来说影响很小！

                // 获取文件大小、文件名等信息
                FileHelperQ.FileInfo fileInfo = FileHelperQ.getFileInfoByUri(parentActivity, originalPhotoForChoose);
                if(fileInfo != null) {
                    Log.i(TAG, "通过Uri获取到了文件信息了，name=" + fileInfo.getName() + "、type=" + fileInfo.getType()
                            + "、size=" + fileInfo.getSize()+"、lastModified="+ fileInfo.getLastModified());

                    // 文件信息不完整判断
                    if(CommonUtils.isStringEmpty(fileInfo.getName(), true) || fileInfo.getSize() <= 0){
                        WidgetUtils.showWithDialog(parentActivity, parentActivity.getResources().getString(R.string.general_error), "无效的文件名或大小，本次发送未能继续！");
                        return;
                    }

                    // 允许发送的最大文件大小的检查
                    if(fileInfo.getSize() > Const.SEND_FILE_DATA_MAX_LENGTH || fileInfo.getSize() <= 0) {
                        Log.w(TAG, "【SendFile】要发送的文件["+originalPhotoForChoose+"]大小非法，(MAX="+ Const.SEND_FILE_DATA_MAX_LENGTH +"字节)，本地发送没有继续！");
                        WidgetUtils.showWithDialog(parentActivity, "文件超限提示", "Uri指向的文件过大，当前允许最大发送"
                                +CommonUtils.getConvenientFileSize(Const.SEND_FILE_DATA_MAX_LENGTH, 0) +"，本次发送已取消！");
                        return;
                    }

                    // 开始分区存储中从公共目录中读取文件到私有目录中，以备接下来的文件上传完整流程
                    try{
                        // 复制文件使用异步线程来执行可以提升用户体验
                        new DataLoadingAsyncTask2<Uri, Integer, String>(parentActivity, "文件读取中，请稍候..") {
                            @Override
                            protected String doInBackground(Uri... params) {
                                try {
                                    String fileName = fileInfo.getName();
                                    // 要复制到的位置（对于Andriod10及以上系统来说，只有私有目录是可以通过File方式读写的，所以文件发送前必须从手机受保护的公有目录中复制才能读取到文件的完整信息）
                                    String copyToPath = ReceivedFileHelper.getReceivedFileSavedDirHasSlash()+fileName;

                                    // ** 以下是专为图片文件添中的专门逻辑（目希望从sd卡中选择的图片文件，在发送时能进入图片消息处理逻辑，而不是当成普通用文件一概而论，从而提升用户体验！）
                                    boolean isImageFile = SendFileHelper.isImageFile(fileName);
                                    // 如果选择的是图片文件，则先要复制一份到图片消息的临时目录中（因为接下来的图片消息处理逻辑中，会涉及到压缩等，如不复制一份，那原始文件不就被破坏掉了吗？！）
                                    if(isImageFile) {// for image START
                                        copyToPath = SendImageWrapper.createTempImageFileLocation();
                                    }// for image END

                                    if (copyToPath == null) {
                                        WidgetUtils.showToast(parentActivity, parentActivity.getString(R.string.chat_sendpic_image_sdcar_error), WidgetUtils.ToastType.WARN);
                                        return null;
                                    }

                                    final File copyToFile = new File(copyToPath);

                                    // 如果本地已存在这个文件，就不需要复制了
                                    boolean needCopy = true;
                                    if(copyToFile.isFile() && copyToFile.exists()){
                                        // 如果该文件的关键特征相同即表示已存在于本地私有目录了（下面就不需要进行复制了）
                                        // 补充说明：文件被复制后，它的复制时间会导致lastModified变更，所以这个字段不能作为判定依据
                                        if(fileInfo.getSize() == copyToFile.length()){// && fileInfo.getLastModified() == copyToFile.lastModified()
                                            needCopy = false;
                                        }
                                    }

                                    Log.i(TAG, "Uri文件[" + params[0] + "]的是否已存在于私有目录？【"+!needCopy+"】" +
                                            "，fileInfo.getSize()="+fileInfo.getSize()+"/copyToFile.length()="+copyToFile.length()
                                            +"、fileInfo.getLastModified()="+fileInfo.getLastModified()+"/copyToFile.lastModified()="+copyToFile.lastModified());

                                    if(needCopy) {
                                        Log.i(TAG, "Uri文件[" + params[0] + "]需要复制，复制马上开始。。。。");

                                        long t1 = System.currentTimeMillis();

                                        // 开始复制
                                        boolean ok = FileHelperQ.copyFile(params[0], copyToFile);
                                        if (ok) {
                                            Log.i(TAG, "Uri文件[" + params[0] + "]复制到私有目录完成【成功】，总耗时" + (System.currentTimeMillis() - t1) + "毫秒，复制后的位置是：" + copyToPath);
                                            return copyToPath;
                                        } else {
                                            Log.i(TAG, "Uri文件[" + params[0] + "]复制到私有目录完成【失败】!");
                                        }
                                    }
                                    else{
                                        Log.i(TAG, "Uri文件[" + params[0] + "]无需复制，直接进入发送流程！");
                                        return copyToPath;
                                    }

                                    return null;
                                }
                                catch(Exception e) {
                                    Log.w(TAG, e);
                                    return null;
                                }
                            }

                            @Override
                            protected void onPostExecuteImpl(Object result) {
                                if(parentActivity != null) {
                                    String copyToPath = null;
                                    if (result != null && result instanceof String) {
                                        copyToPath = (String) result;

                                        // 从公共目录中复制完成，开始真正进入大文件的发送处理逻辑（从APP的私有目录读取哦，不再受AndriodQ分区存储限制了）
//                                      new SendFileProcessor(parentActivity, usedFor, toId, toName, copyToPath).doSend();
                                        sendFileImpl(parentActivity, usedFor, toId, toName, copyToPath);
                                    } else {
                                        Log.w(TAG, "文件复制完成，但完成后的copyToPath==null，有可能是复制失败了！");
                                        WidgetUtils.showToast(context, "Uri文件数据读取失败，发送已被取消！", WidgetUtils.ToastType.WARN);
                                    }
                                }
                            }
                        }.execute(originalPhotoForChoose);
                    }
                    catch (Exception e){
                        Log.w(TAG, e);
                        WidgetUtils.showWithDialog(parentActivity, parentActivity.getResources().getString(R.string.general_error), "Uri文件数据准备时出现异常，本次发送已取消！");
                        return;
                    }
                }
                else
                    Log.w(TAG, "通过Uri获取到的Uri信息为null，发送没有继续！");
            }
//            else{
//                Log.w(TAG, "未知的文件选择requestCode="+requestCode+", 发送文件无法继续！");
//            }
        }
    }

    /**
     * 文件发送的实施方法（方法内将按文件类型自动进行相应的文件消息处理）。
     *
     * @param parentActivity 父activity
     * @param usedFor 参数说明请见：{@link SendFileProcessor#usedFor}
     * @param toId 参数说明请见：{@link SendFileProcessor#toId}
     * @param toName 参数说明请见：{@link SendFileProcessor#toName}
     * @param filePath 视频文件绝对路径（在Android10及以上系统中，此路径只可能是APP的私有路径，因为Android10及
     *                 以后系统由于分区存储权限限制，不允许直接读取系统目录）
     */
    private static void sendFileImpl(Activity parentActivity, String usedFor, String toId, String toName, String filePath) {
        if(filePath != null) {
            String fileName = FileHelper.getFileName(filePath);
            // 如果发送的是视频，则走短视频消息发送逻辑
            if (SendFileHelper.isVideoFile(fileName)) {
                long duration = ToolKits.getVideoDuration(filePath);
                // 自动进入短视频消息的发送处理逻辑
                new SendShortVideoProcessor(parentActivity, usedFor, toId, toName, filePath, duration, false, false).doSend();
            }
            // 如果发送的是图片，则走图片消息发送逻辑
            else if(SendFileHelper.isImageFile(fileName)){
                // 自动进入图片消息的发送处理逻辑
                parentActivity.startActivity(IntentFactory.createPreviewAndSendActivityIntent(parentActivity, ImageViewActivity.ImageDataType.FILE_PATH, filePath, toId, usedFor));
            }
            // 普通文件就走正常的大文件发送逻辑
            else {
                // 余下的说是普通大文件消息的发送处理逻辑
                new SendFileProcessor(parentActivity, usedFor, toId, toName, filePath).doSend();
            }
        } else {
            Log.w(TAG, "无效的参数，filePath==null！");
        }
    }

    /**
     * 实现大文件消息中的大文件数据数据上传（支持断点续传逻辑），以及上传完成后的处理等全流程。
     * 
     * @param context
     * @param fileName 要上传的文件名
     * @param filePath 要上传文件的完整路径
     * @param fileMd5 文件的md5码
     * @param cme 对应聊天界面中一条大文件消息的数据模型
     * @param observerForFileUploadProcessOK 观察者：用于文件上传完成时通知本方法的调用者来做余下的事（把这个观察者当回调来理解就好了）
     */
    public static void processBigFileUpload(Context context, final String fileName
            , final String filePath, final String fileMd5, final Message cme
            , final Observer observerForFileUploadProcessOK)
    {
        // 文件上传状态监听器
        final BigFileUploadTaskListenerForChat utl = new BigFileUploadTaskListenerForChat(context, cme) {
            @Override
            protected void notificateFileUploadedSucessObserver() {
                // 文件上传已完成，通知观察者可以做余下的事了
                if(observerForFileUploadProcessOK != null)
                    observerForFileUploadProcessOK.update(null, null);
            }
        };
        
        final BigFileUploadManager um = MyApplication.getInstance(context).getIMClientManager().getBigFileUploadManager();
        if(um != null)
        {
            // 检查文件是否正在上传中（确保相同md5的任务只有一个在上传中）
            if(um.isUploading(fileMd5))
            {
                Log.w(TAG, "【大文件上传】要上传大文件："+filePath+"， 已存在相同的上传任务，本次任务没有继续！");
                utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
                return;
            }

            // 检查参数的合法性
            if (filePath == null || fileName == null || fileMd5 == null)
            {
                Log.w(TAG, "【大文件上传】相关参数不能为空，本次上传取消（filePath="+filePath
                        +", fileName="+fileName+", fileMd5="+fileMd5+"）!");
                utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
                return;
            }

            // TODO: 文件是否存在、文件大小、文件长度等前置检查已经在SendFileProcessor中做过了，本方法中就不需要重复做了

            // 本地用户信息
            final RosterElementEntity localUser = MyApplication.getInstance(context).getIMClientManager().getLocalUserInfo();
            if (localUser != null)
            {
                //** 首先向服务端提交大文件信息查询请求（看看是否需要上传，或者要从第几块开始上传）
                new AsyncTask<String, Integer, DataFromServer>() {// 网络请示运行在异步线程中提升体验
                    @Override
                    protected DataFromServer doInBackground(String... strings) {
                        return HttpRestHelper.queryBigFileInfoFromServer(fileMd5, localUser.getUser_uid(), BigFileType.FILE_TYPE_COMMON_BIG_FILE);
                    }

                    protected void onPostExecute(DataFromServer dfs) {
                        // 本次开始上传的分块索引起始块
                        int startChunck = 1;
                        
                        if (dfs == null || !dfs.isSuccess()) {
                            Log.w(TAG, "【大文件上传】从服务端查询该文件的信息失败了，原因是：" + dfs.getReturnValue() + "，本次任务没有继续！（filePath=" + filePath + ", fileName=" + fileName + ", fileMd5=" + fileMd5 + "）");
                            utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
                            return;
                        } else {
                            if (dfs.getReturnValue() != null) {
                                // 返回结果详见http文档中“【接口1015-23-7】”的详细说明
                                JSONObject ret = HttpRestHelper.parseBigFileInfoFromServer((String) dfs.getReturnValue());

                                // 服服务返回的查询结果码（详见http文档中“【接口1015-23-7】”的详细说明）：
                                // * 0 表示该文件不存在(未被上传过)
                                // * 1 表示该文件已经存在且已上传完成（无需再次上传）
                                // * 2 表示该文件已经存在查未上传完成（此时chunkCountInServer才有意义）
                                String retCode = ret.getString("retCode");
                                // 该文件在服务端已传完的分块个数（为>=0的整数）
                                String chunkCountInServer = ret.getString("chunkCount");

                                Log.w(TAG, "【大文件上传】从服务端查询该文件的断点续传信息成功返回，数据结果：retCode=" + retCode + ", chunkCountInServer="
                                        + chunkCountInServer + "（filePath=" + filePath + ", fileName=" + fileName + ", fileMd5=" + fileMd5 + "）");

                                // 该md5码对应的文件已经存在于服务器上了（不需要重复上传）
                                if ("1".equals(retCode)) {
                                    Log.i(TAG, "【大文件上传】大文件：" + filePath + "已经存在于服务器，本次不需要重复上传文件了！【END】");
                                    utl.onUploadSuccess(fileName, fileMd5, filePath, -1, -1);
                                    return;
                                }
                                // 表示该文件已经存在查未上传完成（此时chunkCountInServer才有意义）
                                else if ("2".equals(retCode)) {
                                    // 比如“chunkCountInServer”=2时，表示当前服务端已经上传了2块，但因为无法确定最后一块
                                    // 是否已被正常上传完成，所以本次的上传应该重传这最后一块，即本次应从“第2块”开始传
                                    startChunck = CommonUtils.getIntValue(chunkCountInServer, 1);

                                    Log.i(TAG, "【大文件上传】大文件：" + filePath + "的第"+chunkCountInServer
                                            +"块已经上传，本次将从第"+startChunck+"块续传。。。");
                                }
                                // 表示该文件不存在(未被上传过)
                                else {
                                    Log.d(TAG, "【大文件上传】大文件：" + filePath + "从未上传过，本次将从第"+startChunck+"块开始从头上传。。。。。。");
                                }
                            } else {
                                Log.w(TAG, "【大文件上传】从服务端查询该文件的断点续传信息成功返回了，但返回结果为空，" + "，本次任务没有继续！（filePath=" + filePath + ", fileName=" + fileName + ", fileMd5=" + fileMd5 + "）");
                                utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
                                return;
                            }
                        }

                        //** 检查完成，开始决定文件数据的上传了
                        if (um != null) {
                            Log.i(TAG, "【大文件上传】要上传的大文件路径是：" + filePath + "， 上传马上开始.......");

                            // 新建一个上传任务
                            BigFileUploadTask.Builder ub = new BigFileUploadTask.Builder();
                            ub.setFileName(fileName);
                            ub.setId(fileMd5);
                            ub.setUrl(Const.BIG_FILE_UPLOADER_CONTROLLER_URL_ROOT);
                            ub.setFilePath(filePath);
                            ub.setChunck(startChunck);// 1 is base start chunck
                            ub.setFileMd5(fileMd5);

                            // 为任务添加监听器
                            ub.setListener(utl);

                            // 加入任务并由开始线程调度和执行
                            um.addUploadTask(ub.build());
                        } else {
                            Log.w(TAG, "【大文件上传】UploadManager不能是null，本次上传取消!");
                            utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
                            return;
                        }
                    }
                }.execute();
                
                return;
            } else {
                Log.w(TAG, "【大文件上传】上传大文件时，localUser不应为null，本次上传取消!");
                utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
                return;
            }
        } else {
            utl.onError(fileName, fileMd5, filePath, -1, -1, -1);
            WidgetUtils.showWithDialog(context, "提示", "文件上传失败，请重启APP后再试！");
            return;
        }
    }

    public static int getFileIconDrawableId(String fileName)
    {
        int resId = R.drawable.file_type_unknow;
        
        String fileExtName = FileHelper.getFileExName(fileName);
        if(fileExtName != null)
        {
            fileExtName = fileExtName.toLowerCase();
            
            if("xls".equals(fileExtName) || "xlsx".equals(fileExtName))
                resId = R.drawable.file_type_excel;
            else if("gif".equals(fileExtName))
                resId = R.drawable.file_type_gif;
            else if("html".equals(fileExtName) || "htm".equals(fileExtName))
                resId = R.drawable.file_type_html;
            else if("jpg".equals(fileExtName) || "jpeg".equals(fileExtName))
                resId = R.drawable.file_type_jpg;
            else if("mp4".equals(fileExtName))
                resId = R.drawable.file_type_mp4;
            else if("pdf".equals(fileExtName))
                resId = R.drawable.file_type_pdf;
            else if("png".equals(fileExtName))
                resId = R.drawable.file_type_png;
            else if("ppt".equals(fileExtName) || "pptx".equals(fileExtName))
                resId = R.drawable.file_type_ppt;
            else if("rar".equals(fileExtName))
                resId = R.drawable.file_type_rar;
            else if("txt".equals(fileExtName))
                resId = R.drawable.file_type_txt;
            else if("apk".equals(fileExtName))
                resId = R.drawable.file_type_apk;
            else if("doc".equals(fileExtName) || "docx".equals(fileExtName))
                resId = R.drawable.file_type_word;
            else if("zip".equals(fileExtName)
                    ||"7z".equals(fileExtName)
                    ||"gz".equals(fileExtName)
                    ||"tar".equals(fileExtName))
                resId = R.drawable.file_type_zip;
        }
        
        return resId;
    }

    /**
     * 是图片文件。
     *
     * @param fileName 图片文件名
     * @return true表示是，否则不是
     */
    public static boolean isImageFile(String fileName) {
        if(fileName != null) {
            String fileExtName = FileHelper.getFileExName(fileName).toLowerCase();
            fileExtName = fileExtName.toLowerCase();
            return "gif".equals(fileExtName)
                    || "jpg".equals(fileExtName)
                    || "jpeg".equals(fileExtName)
                    || "png".equals(fileExtName);
        }
        return false;
    }

    /**
     * 是视频文件。
     *
     * @param fileName 视频文件名
     * @return true表示是，否则不是
     */
    public static boolean isVideoFile(String fileName) {
        if(fileName != null) {
            String fileExtName = FileHelper.getFileExName(fileName).toLowerCase();
            return "mpg".equals(fileExtName)
                    || "mpeg".equals(fileExtName)
                    || "3gp".equals(fileExtName)
                    || "mp4".equals(fileExtName)
                    || "avi".equals(fileExtName)
                    || "mpe".equals(fileExtName);
        }
        return false;
    }

    /**
     * 大文件上传状态监听器实现类（专用于聊天界面中）。
     * 
     * @since 4.3
     */
    private static abstract class BigFileUploadTaskListenerForChat implements BigFileUploadTaskListener
    {
        private Context context = null;
        private Message entityInChatListView = null;
        
        
        public BigFileUploadTaskListenerForChat(Context context, Message entityInChatListView)
        {
            this.context = context;
            this.entityInChatListView = entityInChatListView;
        }

        @Override
        public void onUploading(String fileName, String fileMd5, String fileFullPath
                , int percent, int chunck, int chuncks)
        {
            Log.w(TAG, "【大文件上传-onUploading-["+(chunck-1)+"/"+chuncks+"]-%"+percent+"】"
                    + fileName + ",上传进度：" + percent);

            // 更新本次文件上传任务所对应的聊天列表中的数据单元对象
            entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processing);
            entityInChatListView.setSendStatusSecondaryProgress(percent);// 0~100整数

            // 通知观察者，尝试刷新UI界面上的上传进度显示
            notificateStatusChangedObserver(fileName, fileMd5, fileFullPath);
        }

        @Override
        public void onUploadSuccess(String fileName, String fileMd5, String fileFullPath, int chunck, int chuncks)
        {
            Log.w(TAG, "【大文件上传-onUploadSuccess】" + fileName + ", chunk/chunks=" + (chunck - 1) + "/" + chuncks);

            // 更新本次文件上传任务所对应的聊天列表中的数据单元对象
            entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processOk);
            entityInChatListView.setSendStatusSecondaryProgress(100);// 0~100整数

            // 通知观察者，尝试刷新UI界面上的上传进度显示
            notificateStatusChangedObserver(fileName, fileMd5, fileFullPath);
            
            // 上传任务成功完成后的通知
            notificateFileUploadedSucessObserver();
            
            Log.e(TAG, "TODO 【大文件上传成功了，该向对方发送文件消息了！！！！！！】");
        }

        @Override
        public void onError(String fileName, String fileMd5, String fileFullPath
                , int errorCode, int chunck, int chuncks)
        {
            Log.w(TAG, "【大文件上传-onError】errorCode=" + errorCode + ",file=" + fileName + ", chunk/chunks=" + (chunck - 1) + "/" + chuncks);

            // 更新本次文件上传任务所对应的聊天列表中的数据单元对象
            entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processFaild);
            // 并直接像微信一样标识整个消息的发送状态为失败
            entityInChatListView.setSendStatus(Message.SendStatus.sendFaild);

            // 通知观察者，尝试刷新UI界面上的上传进度显示
            notificateStatusChangedObserver(fileName, fileMd5, fileFullPath);
        }

        @Override
        public void onPause(String fileName, String fileMd5, String fileFullPath, int chunck, int chuncks)
        {
            Log.w(TAG, "【大文件上传-onPause】" + fileName + ", chunk/chunks=" + (chunck - 1) + "/" + chuncks);
        }
        
        private void notificateStatusChangedObserver(String fileName, String fileMd5, String fileFullPath)
        {
            // 通知观察者，尝试刷新UI界面上的上传进度显示
            Observer fileStatusChangedObserver = MyApplication.getInstance(this.context)
                    .getIMClientManager().getBigFileUploadManager().getFileStatusChangedObserver();
            if(fileStatusChangedObserver != null)
                fileStatusChangedObserver.update(null, new Object[]{fileName, fileMd5, fileFullPath});
        }

        /**
         * 上传任务成功完成后的通知。
         */
        protected abstract void notificateFileUploadedSucessObserver();
    }
}
